package com.dingwen.treasure.file.component;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import com.dingwen.treasure.file.config.M3U8Properties;
import com.dingwen.treasure.file.service.impl.SystemFileServiceImpl;
import com.dingwen.treasure.tkmybatis.service.TreasureFileService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import net.bramp.ffmpeg.FFmpeg;
import net.bramp.ffmpeg.FFmpegExecutor;
import net.bramp.ffmpeg.FFprobe;
import net.bramp.ffmpeg.builder.FFmpegBuilder;
import net.bramp.ffmpeg.probe.FFmpegProbeResult;
import net.bramp.ffmpeg.probe.FFmpegStream;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * 　M3u8Component
 * 　@author dingwen
 * 　@date 2022/7/27
 */
@Component
@Slf4j
public class M3u8Component {
    /**
     * fFmpeg
     */
    @Resource
    private FFmpeg fFmpeg;


    /**
     * fFprobe
     */
    @Resource
    private FFprobe fFprobe;


    /**
     * systemFileService
     */
    @Resource
    private SystemFileServiceImpl systemFileService;

    /**
     * treasureFileService
     */
    @Resource
    private TreasureFileService treasureFileService;


    /**
     * m3U8Properties
     */
    @Resource
    private M3U8Properties m3U8Properties;


    /**
     * taskExecutor
     */
    @Resource
    private ThreadPoolTaskExecutor taskExecutor;

    /**
     * 执行上传
     *
     * @param multipartFile 文件
     */
    public void executeUpload(MultipartFile multipartFile) {
        log.info("开始进行视频切片上传...");
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        CompletableFuture
                .supplyAsync(() -> uploadToTemp(multipartFile), taskExecutor)
                .thenApplyAsync(pair -> new Pair<>(pair.getKey(), convertToM3U8(pair.getKey(), pair.getValue())), taskExecutor)
                .thenApplyAsync(pair -> {
                    treasureFileService.save(pair.getValue(), m3U8Properties.getPlayBaseUrl().concat(pair.getValue()), multipartFile);
                    return pair.getKey();
                }, taskExecutor)
                .whenCompleteAsync((tempFilePath, ex) -> {
                    cleanTempFile(tempFilePath);
                    stopWatch.stop();
                    log.info("视频切片上传完成，耗时：{}毫秒", stopWatch.getTotalTimeMillis());
                }, taskExecutor)
                .exceptionally(ex -> {
                    log.error(ex.getMessage());
                    return null;
                });
    }

    /**
     * 上传文件到临时目录
     *
     * @param multipartFile 文件
     * @return {@link String} 临时文件目录
     */
    public Pair<String, String> uploadToTemp(MultipartFile multipartFile) {
        log.info("step1：上传文件到临时目录...");
        return systemFileService.upload(multipartFile);
    }

    /**
     * 将临时文件转换为M3U8切片
     *
     * @param tempFilePath 临时文件路径
     * @param m3u8FilePath m3u8文件路径
     * @return {@link String}
     */
    @SneakyThrows(Throwable.class)
    public String convertToM3U8(String tempFilePath, String m3u8FilePath) {
        log.info("step2：将临时文件转换为M3U8切片...");
        FFmpegProbeResult probe = fFprobe.probe(tempFilePath);

        // 全部流数据
        List<FFmpegStream> streams = probe.getStreams()
                .stream()
                .filter(fFmpegStream -> ObjectUtil.isNotNull(fFmpegStream.codec_type))
                .collect(Collectors.toList());
        // 音频数据
        Optional<FFmpegStream> audioStream = streams
                .stream()
                .filter(fFmpegStream -> FFmpegStream.CodecType.AUDIO.equals(fFmpegStream.codec_type))
                .findFirst();
        // 视频数据
        Optional<FFmpegStream> videoStream = streams.stream()
                .filter(fFmpegStream -> FFmpegStream.CodecType.VIDEO.equals(fFmpegStream.codec_type))
                .findFirst();

        if (!audioStream.isPresent() || !videoStream.isPresent()) {
            log.error("未发现音频流或视频流");
            return null;
        }

        //m3u8文件 存储路径
        String mainFileName = IdUtil.getSnowflakeNextIdStr();
        m3u8FilePath = m3u8FilePath.concat(mainFileName);
        if (!FileUtil.exist(m3u8FilePath)) {
            FileUtil.mkdir(m3u8FilePath);
        }
        String m3u8FileName = m3u8FilePath.concat(File.separator).concat(mainFileName).concat(".m3u8");
        FFmpegBuilder builder = new FFmpegBuilder()
                .setInput(tempFilePath)
                .overrideOutputFiles(true)
                //输出文件
                .addOutput(m3u8FileName)
                //"mp4"
                .setFormat(probe.getFormat().format_name)
                .setAudioBitRate(audioStream.map(fFmpegStream -> fFmpegStream.bit_rate).orElse(0L))
                .setAudioChannels(1)
                //using the aac codec
                .setAudioCodec("aac")
                .setAudioSampleRate(audioStream.get().sample_rate)
                .setAudioBitRate(audioStream.get().bit_rate)
                .setStrict(FFmpegBuilder.Strict.STRICT)
                .setFormat("hls")
                .setPreset("ultrafast")
                .addExtraArgs("-profile:v", "baseline")
                .addExtraArgs("-level", "3.0")
                .addExtraArgs("-start_number", "0")
                .addExtraArgs("-hls_time", "10")
                .addExtraArgs("-hls_list_size", "10")
                .addExtraArgs("-f", "hls")
                .done();

        FFmpegExecutor executor = new FFmpegExecutor(fFmpeg, fFprobe);
        // Run a one-pass encode
        executor.createJob(builder).run();
        return m3u8FileName.substring(m3u8FileName.lastIndexOf(File.separator));
    }

    /**
     * 清除临时文件
     */
    public void cleanTempFile(String tempFilePath) {
        FileUtil.del(tempFilePath);
        log.info("step 4 清除临时文件");
    }


}
